#include <windows.h>
#include <string.h>
#include <malloc.h>
#include <math.h>
#include <typeinfo.h>

#include "lib.h"
#include "libglob.h"
#include "bitmap.h"
#include "frame.h"
#include "screen.h"

#include "fontfactory.h"
#include "currencycodepage.h"
#include "textblock.h"
#include "systemfont.h"

//----------------------------------------------------------------------------
// Public
//
TextBlock::TextBlock()
{
	initialize();
}

TextBlock::TextBlock(const String & aFont, uint32 width, uint32 height)
{
	initialize();

	font(aFont);
	blockSize(width, height);
}

TextBlock::TextBlock(const String & aFont, uint32 width, uint32 height,
	const String & txt)
{
	initialize();

	font(aFont);
	blockSize(width, height);
	text(txt);						// Calling this last delays formatText!
}

// Deprecated
TextBlock::TextBlock(const String & aFont, uint32 width, uint32 height,
	const uint16 * txt)
{
	initialize();

	font(aFont);
	blockSize(width, height);
	text(txt);						// Calling this last delays formatText!
}

TextBlock::~TextBlock()
{
	delete mFont;
	mFont = 0;

	delete [] mText;
	mText = 0;

	if (mBitmap)
	{
		deleteCBitmap(mBitmap);
		mBitmap = 0;
	}

	delete [] mIB.ibText;
	mIB.ibText = 0;

	delete [] mIB.ibLines;
	mIB.ibLines = 0;
}

pCBitmap
TextBlock::bitmap()
{
	buildBitmap();

	return mBitmap;
}

uint32
TextBlock::bitmapHeight()
{
	return mBitmapHeight;
}

uint32
TextBlock::bitmapWidth()
{
	return mBitmapWidth;
}

void
TextBlock::blockHeight(uint32 height)
{
	// Only call formatText if something actually changed!
	//
	if (mBlockHeight != height)
	{
		mBlockHeight = height;

		formatText();
	}
}

uint32
TextBlock::blockHeight()
{
	return mBlockHeight;
}

void
TextBlock::blockWidth(uint32 width)
{
	// Only call formatText if something actually changed!
	//
	if (mBlockWidth != width)
	{
		mBlockWidth = width;

		formatText();
	}
}

uint32
TextBlock::blockWidth()
{
	return mBlockWidth;
}

void
TextBlock::blockSize(uint32 width, uint32 height)
{
	// Only call formatText if something actually changed!
	//
	if (mBlockWidth != width || mBlockHeight != height)
	{
		mBlockWidth = width;
		mBlockHeight = height;

		formatText();
	}
}

void
TextBlock::fontSize(uint32 size)
{
	// Only call formatText if something actually changed!
	//
	if (mFontSize != size)
	{
		mFontSize = size;

		formatText();
	}
}

void
TextBlock::fontSize( FontSize size )
{
	fontSize( static_cast<uint32>( size ) );
}

uint32
TextBlock::fontSize()
{
	return mFontSize;
}

uint32
TextBlock::fontSizeRendered()
{
	return mFontSizeRendered;
}

void
TextBlock::draw(pCFrame pFrame, int32 x, int32 y)
{
	buildBitmap();

	if (mBitmap && pFrame)
		CFrameTransparentBitmapBlt(pFrame, x, y, mBitmap);
}


void
TextBlock::font(const String & aFont)
{
	if (mFontName == aFont)
		return;

	mFontName = aFont;

	delete mFont;
	mFont = 0;

	mFont = FontFactory::create(aFont);

	formatText();
}

Font*
TextBlock::font()
{
	return mFont;
}

const String &
TextBlock::fontName()
{
	return mFontName;
}

void
TextBlock::horizontalPosition(HorizontalPosition where)
{
	// Only call formatText if something actually changed!
	//
	if (mHorizontalPosition != where)
	{
		mHorizontalPosition = where;

		formatText();
	}
}

TextBlock::HorizontalPosition
TextBlock::horizontalPosition()
{
	return mHorizontalPosition;
}

// Justification has not yet been implemented.
void
TextBlock::justification(Justification how)
{
	// Only call formatText if something actually changed!
	//
	if (mJustification != how)
	{
		mJustification = how;

		formatText();
	}
}

// Justification has not yet been implemented.
TextBlock::Justification
TextBlock::justification()
{
	return mJustification;
}

void
TextBlock::text(const String & text)
{
	this->text((const uint16*)text);
}

#if 0
const String &
TextBlock::text()
{
	return mText;
}
#endif

// Deprecated
void
TextBlock::text(const uint16* text)
{
	// Only call formatText if something actually changed!
	//
	if (mText && text && *mText && *text && wcscmp(text,mText) == 0)
		return;

	delete[] mText;
	mText = new uint16[ wcslen(text) + 1];
	if (mText)
	{
		wcscpy(mText, text);
	}

	formatText();
}

// Deprecated
const uint16*
TextBlock::text()
{
	return mText;
}

void
TextBlock::verticalPosition(VerticalPosition where)
{
	// Only call formatText if something actually changed!
	//
	if (mVerticalPosition != where)
	{
		mVerticalPosition = where;

		formatText();
	}
}

TextBlock::VerticalPosition
TextBlock::verticalPosition()
{
	return mVerticalPosition;
}

//----------------------------------------------------------------------------
// Protected
//
uint16 *
TextBlock::preprocessText(const uint16 * text)
{
	uint16 *	ptr;
	int32	src;
	int32	dst;

	if (!text || !*text)
		return 0;

	ptr = new uint16[ wcslen(text) + 1 ];
	if (!ptr)
		return 0;

	for (src = 0, dst = 0; text[src]; src++)
	{
		if (ignoreLineBreaks() && (text[src] == '\n' || text[src] == '\r'))
		{
			// Chew up initial newlines.
			if (dst == 0)
				continue;

			// Newlines followed with space are removed.
			if (text[src + 1] == ' ')
				continue;

			// Newlines preceeded with space or hyphen are removed.
			if (src && (text[src - 1] == ' ' || text[src - 1] == '-'))
				continue;

			// All other newlines are replaced with a space.
			ptr[dst++] = ' ';
			continue;
		}
		else
		{
			// Convert either newline value to standard '\n'.
			if (text[src] == '\n' || text[src] == '\r')
			{
				ptr[dst++] = '\n';
				continue;
			}
			
			// Convert non-printable characters to a space.
			int32 src_index = src;
			String font_type = typeid(*mFont).name();

			//.fnt Fonts are loaded from resource files and used to display
			//denomination and currency info. Special logic must be done on it
			if(font_type == "class Library::CurrencyCodepage")
			{
                CurrencyCodepage* cFont = (CurrencyCodepage*)mFont;
                int32 ofs = cFont->charOffset(text[src],&text[src],&src);

                if ( ofs == Font::NOCHAR )
				{
					ptr[dst++] = ' ';
					continue;
				}

                // Fogbugz 20216 -- Ringgit (RM) currency
                // An 'R' can be either Rand or Ringgit.  Unforuntately
                // our multi-character currency symbol logic assumed
                // that the first character would be unique, so we need
                // to special case RM here.  The actual substition of
                // the RM symbol occurs inside currencycodepage::drawText.
                if ( ( text[src_index] == 'R' ) && ( text[src] == 'M' ) )
                {
                    ptr[dst++] = 'R';
                    ptr[dst++] = 'M';
                    continue;
                }
			}
            else if (font_type == "class SystemFont")
            {
                SystemFont* sysFont = (SystemFont*) mFont;
                ptr[dst++] = sysFont->charOffset(text[src]);
                continue;
			}
			else
			{
				if (mFont->charOffset(text[src]) == Font::NOCHAR)
				{
					ptr[dst++] = ' ';
					continue;
				}
			}

			// Normal situation.
			ptr[dst++] = text[src_index];
		}
	}

	ptr[dst] = '\0';
	return ptr;
}

uint16 *
TextBlock::skipWhitespace(uint16 * text)
{
	if (!text || !*text)
		return 0;

	while (*text == ' ')
		text++;

	return text;
}

uint16 *
TextBlock::findWord(uint16 * text, uint16 ** start, uint16 ** end)
{
	// Check for invalid input.
	if (!text || !*text)
		return 0;

	text = skipWhitespace(text);

	// Check for empty strings.
	if (!text || !*text)
		return 0;

	*start = text;

	if (*text == '\n')
		*end = text++;
	else
	{
		do {
			*end = text++;
		} while (*text && *text != ' ' && *text != '\n' &&
			/* Languages with line breaks on any character */
			!(*text >= 0x4E00 && *text <= 0x9FBF) &&    // CJK Unified Ideographics
			!(*text >= 0x3400 && *text <= 0x4DBF) &&    // CJK Unified Ideographics Extension A
			!(*text >= 0x20000 && *text <= 0x2A6DF) &&  // CJK Unified Ideographics Extension B
			!(*text >= 0xF900 && *text <= 0xFAFF) &&    // CJK Compatibility Ideographics
			!(*text >= 0x2F800 && *text <= 0x2FA1F)     // CJK Compatibility Ideographics Supplement
			);
	}

	return text;
}

uint32
TextBlock::wordCount(uint16 * text)
{
	uint32	count;
	uint16 *	s;
	uint16 *	e;

	if (!text || !*text)
		return 0;

	count	= 0;
	text	= findWord(text, &s, &e);

	while (text)
	{
		count++;
		text = findWord(text, &s, &e);
	}

	return count;
}

void
TextBlock::formatText()
{
	uint16 *	text = NULL;
	uint16 *	ptr = NULL;
	Line *		lines = NULL;
	uint32		linesTotal = 0;
	uint32		linesValid = 0;
	uint32		width = 0;
	uint32		height = 0;
	uint32		i = 0;
	uint32		j = 0;
	float		scale = 1.0f;
	uint32		firstLine = 0;
	uint32		lastLine = 0;
	int32		graphicWidth = 0;
	int32		graphicHeight = 0;
	int32		textWidth = 0;
	int32		textHeight = 0;


	// Clean up first.  mBitmapHeight and mBitmapWidth will be set back to
	// valid values at the end of this method (barring any errors).
	//
	if (mBitmap)
	{
		deleteCBitmap(mBitmap);
		mBitmap			= 0;
		mBitmapHeight	= 0;
		mBitmapWidth	= 0;
	}

	// Clean up first.  mIB.ibText and mIB.ibLines will be set back to valid
	// values at the end of this method (barring any errors).
	//
	delete [] mIB.ibText;
	mIB.ibText = 0;

	delete [] mIB.ibLines;
	mIB.ibLines = 0;

	// Since we just toasted all of the information (above), set the state
	// to invalid until we establish new information.
	//
	mIB.ibState = IncrementalBuild::INVALID;

	mFontSizeRendered = 0;

	// Stopping point.  It's not possible to format non-existant data!
	//
	if (!mFont || !mText || !*mText )
		return;

	if (mFontSize == FS_AUTOSIZE && (mBlockWidth == BS_AUTOSIZE || mBlockHeight == BS_AUTOSIZE))
		return;

	text = preprocessText(mText);
	if (!text)
		return;

	uint32 fontSize = mFontSize;
	if (fontSize == FS_AUTOSIZE)
	{
		Line line;
		memset(&line, 0, sizeof(Line));

		bool8 current = ignoreLineBreaks();

		ignoreLineBreaks(true);

		// Populate one line.
		line.start = preprocessText(mText);
		line.end = line.start + wcslen(line.start);
		line.validLine = true;

		ignoreLineBreaks(current);

		line.calculateMetrics(mFont);

		delete[] line.start;
		line.start = 0;

		textHeight = mFont->maximumTextHeight();

		graphicHeight = textHeight;
		graphicHeight -= line.glyphTop;
		graphicHeight -= line.glyphBottom;

		width = line.glyphWidth;

		switch (verticalPosition())
		{
			case VP_TOP_TEXT:
			case VP_CENTER_TEXT:
			case VP_BOTTOM_TEXT:
				height = line.textHeight;
				break;

			case VP_TOP_GFX:
			case VP_CENTER_GFX:
			case VP_BOTTOM_GFX:
				height = graphicHeight;
				break;
		}

		uint32 sa1 = width * height;
		uint32 sa2 = mBlockWidth * mBlockHeight;
		if (sa1 < sa2)
			fontSize = mFont->maximumTextHeight();
		else
		{
			fontSize = (uint32)(sa2 / MAX(width / height, 1));
			fontSize = (uint32)sqrt( (double)fontSize );
		}
	}

	linesTotal = wordCount(text);
	if ( linesTotal )
	{
		lines = new Line[ linesTotal ];
	}

	if ( !lines )
	{
		delete [] text;
		return;
	}

	bool8 textFit = false;
	while (!textFit)
	{
		memset(lines, 0, sizeof(Line) * linesTotal);

		// Populate each line with just a single word.
		i   = 0;
		ptr = findWord(text, &lines[i].start, &lines[i].end);
		while (ptr)
		{
			lines[i++].validLine = true;
			ptr = findWord(ptr, &lines[i].start, &lines[i].end);
		}

		// Scale known block sizes up to account for scaled down font.
		switch (verticalPosition())
		{
			case VP_TOP_TEXT:
			case VP_CENTER_TEXT:
			case VP_BOTTOM_TEXT:
				if (fontSize > (uint32)MAX(mFont->maximumTextHeight(), 0))
					scale = 1;
				else
					scale = (float)mFont->maximumTextHeight() / (float)fontSize;
				break;

			case VP_TOP_GFX:
			case VP_CENTER_GFX:
			case VP_BOTTOM_GFX:
				if (fontSize > (uint32)MAX(mFont->maximumGlyphHeight(), 0))
					scale = 1;
				else
					scale = (float)mFont->maximumGlyphHeight() / (float)fontSize;
				break;
		}

		if (mBlockWidth == BS_AUTOSIZE)
			width = 0xffffffff;
		else
			width = (uint32)(scale * mBlockWidth);

		if (mBlockHeight == BS_AUTOSIZE)
			height = 0xffffffff;
		else
			height = (uint32)(scale * mBlockHeight);

		// Attempt to reform words back into lines with width restrictions.
		for (i = 0, firstLine = 0, linesValid = 0; i < linesTotal; i++)
		{
			// Lines might become invalid during merging - skip.
			if (lines[i].validLine == false)
				continue;
			else
			{
				lastLine = i;
				linesValid++;
			}

			lines[i].start = skipWhitespace(lines[i].start);
			lines[i].calculateMetrics(mFont);

			if (*lines[i].start == '\n')
				continue;

			// Attempt to merge additional words onto this line.
			if ((uint32)MAX(lines[i].glyphWidth, 0) < width)
			{
				for (j = i + 1; j < linesTotal; j++)
				{
					// Hard line break discontinues line merging.
					if (*skipWhitespace(lines[j].start) == '\n')
					{
						lines[j].validLine = false;
						break;
					}

					ptr = lines[i].end;
					lines[i].end = lines[j].end;
					lines[i].calculateMetrics(mFont);

					// Unable to merge line - too long - restore.
					if ((uint32)MAX(lines[i].glyphWidth, 0) > width)
					{
						lines[i].end = ptr;
						lines[i].calculateMetrics(mFont);
						break;
					}
					// Otherwise, merge succeeded.
					else
						lines[j].validLine = false;
				}
			}
		}

		// Calculate maximum graphic and text width.
		for (graphicWidth = 0, textWidth = 0, i = 0; i < linesTotal; i++)
		{
			if (lines[i].validLine == false)
				continue;

			if (graphicWidth < lines[i].glyphWidth)
				graphicWidth = lines[i].glyphWidth;

			if (textWidth < lines[i].textWidth)
				textWidth = lines[i].textWidth;
		}

		// Calculate graphic and text height.
		textHeight = 0;
		textHeight += linesValid * mFont->maximumTextHeight() +
							(linesValid - 1) * mFont->externalLeading();

		graphicHeight = textHeight;
		graphicHeight -= lines[firstLine].glyphTop;
		graphicHeight -= lines[lastLine].glyphBottom;

		// Calculate real bitmap width.
		if (width == 0xffffffff)
			width = graphicWidth;

		// Calculate real bitmap height.
		if (height == 0xffffffff)
		{
			switch (verticalPosition())
			{
				case VP_TOP_TEXT:
				case VP_CENTER_TEXT:
				case VP_BOTTOM_TEXT:
					height = textHeight;
					break;

				case VP_TOP_GFX:
				case VP_CENTER_GFX:
				case VP_BOTTOM_GFX:
					height = graphicHeight;
					break;
			}
		}

		if (mFontSize == FS_AUTOSIZE)
		{
			textFit = true;

			if (mBlockWidth < (graphicWidth / scale))
				textFit = false;		//try again with a smaller font
			else
			{
				switch (verticalPosition())
				{
					case VP_TOP_TEXT:
					case VP_CENTER_TEXT:
					case VP_BOTTOM_TEXT:
						if (mBlockHeight < (textHeight / scale))
							textFit = false;		//try again with a smaller font
						break;

					case VP_TOP_GFX:
					case VP_CENTER_GFX:
					case VP_BOTTOM_GFX:
						if (mBlockHeight < (graphicHeight / scale))
							textFit = false;		//try again with a smaller font
						break;
				}
			}

			if (!textFit)
				fontSize--;
		}
		else
			textFit = true;
	}

	mFontSizeRendered	= fontSize;
	mBitmapHeight		= scale > 1 ? (uint32)(height / scale) : height;
	mBitmapWidth		= scale > 1 ? (uint32)(width / scale) : width;

	mIB.ibState			= IncrementalBuild::VALID;
	mIB.ibText			= text;
	mIB.ibLines			= lines;
	mIB.ibLinesTotal	= linesTotal;
	mIB.ibLinesValid	= linesValid;
	mIB.ibLineFirst		= firstLine;
	mIB.ibLineLast		= lastLine;
	mIB.ibWidth			= width;
	mIB.ibHeight		= height;
	mIB.ibScale			= scale;
	mIB.ibGraphicWidth	= graphicWidth;
	mIB.ibGraphicHeight	= graphicHeight;
	mIB.ibTextWidth		= textWidth;
	mIB.ibTextHeight	= textHeight;
}

void
TextBlock::buildBitmap()
{
	pCBitmap	bm=NULL;
	int32		x=0;
	int32		y=0;
	uint32		i=0;

	if (mIB.ibState != IncrementalBuild::VALID)
		return;

	bm = newCBitmapBlank(mIB.ibWidth, mIB.ibHeight);
#ifdef TEXTBLOCK_DEBUG_RECT
	CBitmapFillRect(bm, 0, 0, mIB.ibWidth, mIB.ibHeight, GETRGB(255, 0, 0));
#endif

	// Determine starting y location.
	switch (verticalPosition())
	{
		case VP_TOP_TEXT:
			y = 0;
			break;

		case VP_TOP_GFX:
			y = -mIB.ibLines[mIB.ibLineFirst].glyphTop;
			break;

		case VP_CENTER_TEXT:
			y = (mIB.ibHeight / 2) - (mIB.ibTextHeight / 2);
			break;

		case VP_CENTER_GFX:
			y = (mIB.ibHeight / 2) - (mIB.ibGraphicHeight / 2) -
					mIB.ibLines[mIB.ibLineFirst].glyphTop;
			break;

		case VP_BOTTOM_TEXT:
			y = mIB.ibHeight - mIB.ibTextHeight;
			break;

		case VP_BOTTOM_GFX:
			y = mIB.ibHeight - mIB.ibGraphicHeight -
					mIB.ibLines[mIB.ibLineFirst].glyphTop;
			break;
	}

	for (i = 0; i < mIB.ibLinesTotal; i++)
	{
		if (mIB.ibLines[i].validLine == false)
			continue;

		switch (horizontalPosition())
		{
			case HP_LEFT_TEXT:
				x = 0;
				break;

			case HP_LEFT_GFX:
				x = -mIB.ibLines[i].glyphLeft - mIB.ibLines[i].textAbcA;
				break;

			case HP_CENTER_TEXT:
				x = (mIB.ibWidth / 2) - (mIB.ibLines[i].textWidth / 2) -
						mIB.ibLines[i].glyphLeft - mIB.ibLines[i].textAbcA;
				break;

			case HP_CENTER_GFX:
				x = (mIB.ibWidth / 2) - (mIB.ibLines[i].glyphWidth / 2) -
						mIB.ibLines[i].glyphLeft - mIB.ibLines[i].textAbcA;
				break;

			case HP_RIGHT_TEXT:
				x = mIB.ibWidth - mIB.ibLines[i].textWidth -
						mIB.ibLines[i].textAbcA;
				break;

			case HP_RIGHT_GFX:
				x = mIB.ibWidth - mIB.ibLines[i].glyphWidth -
						mIB.ibLines[i].glyphLeft - mIB.ibLines[i].textAbcA;
				break;
		}

		if (*mIB.ibLines[i].start != '\n')
			mIB.ibLines[i].paint(mFont, x, y, bm);

		y += mFont->externalLeading() + mFont->maximumTextHeight();
	}

	if (bm && mIB.ibScale > 1)
	{
		mBitmap = ResizeCBitmapColor(bm, (int32)(mIB.ibWidth / mIB.ibScale),
					(int32)(mIB.ibHeight / mIB.ibScale), 1);
		deleteCBitmap(bm);
	}
	else
		mBitmap = bm;

#ifdef TEXTBLOCK_DEBUG_RECT
	free(mBitmap->cAlphaBits);
	mBitmap->cAlphaBits = 0;
#endif

	// Now that the data has been processed, invalidate and delete.
	mIB.ibState = IncrementalBuild::INVALID;

	delete [] mIB.ibLines;
	mIB.ibLines = 0;
}


//----------------------------------------------------------------------------
// Private
//
void
TextBlock::initialize()
{
	memset(this, 0, sizeof TextBlock);

	horizontalPosition(HP_LEFT_TEXT);
	justification(HJ_LEFT_TEXT);
	verticalPosition(VP_TOP_TEXT);

	blockSize(BS_AUTOSIZE, BS_AUTOSIZE);
	fontSize(FS_FULLSIZE);
}

//----------------------------------------------------------------------------
// TextBlock::Line routines
//
void
TextBlock::Line::calculateMetrics(Font * aFont)
{
	uint16	t = 0;
	uint16 *ptr;

	if (!start || !end || !validLine)
		return;

	if ( *end != '\0' )
	{
		t = *(end + 1);
		*(end + 1) = '\0';
	}

	ptr = start;
	while (*ptr == ' ')
		ptr++;

	aFont->textMetric(&textWidth, &textHeight, &textAbcA, &textAbcC, ptr, end - start + 1);
	aFont->glyphMetric(&glyphWidth, &glyphHeight, &glyphLeft, &glyphRight,
				&glyphTop, &glyphBottom, ptr, end - start + 1);

	if ( t )
	{
		*(end + 1) = t;
	}
}

void
TextBlock::Line::paint(Font * aFont, int32 x, int32 y, pCBitmap bitmap)
{
	uint16	t;

	if (!start || !end || !validLine)
		return;

	t = *(end + 1);
	*(end + 1) = '\0';

	if (bitmap)
		aFont->drawText(&x, y, (const uint16*)start, end - start + 1, bitmap);

	*(end + 1) = t;
}
